﻿using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;

namespace MetadataDesignerLibrary
{
    public class MetadataDesigner
    {
        private MethodBodyStreamEncoder _methodBodyStream;

        internal Counter ParameterCounter { get; } = new Counter(0);
        internal RowNumber ParameterNumber { get; } = new RowNumber();
        internal RowNumber FieldDefinitionNumber { get; } = new RowNumber();
        internal RowNumber MethodDefinitionNumber { get; } = new RowNumber();
        internal RowNumber PropertyDefinitionNumber { get; } = new RowNumber();
        internal RowNumber EventDefinitionNumber { get; } = new RowNumber();
        internal RowNumber LocalConstantNumber { get; } = new RowNumber();
        internal RowNumber LocalVariableNumber { get; } = new RowNumber();

        public MetadataBuilder Builder { get; }
        public BlobBuilder ILStream { get; }
        public MetadataRootBuilder RootBuilder => new MetadataRootBuilder(Builder);

        public MetadataDesigner()
        {
            Builder = new MetadataBuilder();
            ILStream = new BlobBuilder();
            _methodBodyStream = new MethodBodyStreamEncoder(ILStream);
        }

        public void WritePEImage(
            Stream peStream,
            MethodDefinitionHandle entryPointHandle,
            PEHeaderBuilder peHeaderBuilder,
            BlobContentId contentId,
            CorFlags corFlags)
        {
            ManagedPEBuilder peBuilder = new ManagedPEBuilder(
                header: peHeaderBuilder,
                metadataRootBuilder: RootBuilder,
                ilStream: ILStream,
                entryPoint: entryPointHandle,
                flags: corFlags,
                deterministicIdProvider: content => contentId);

            BlobBuilder peBlob = new BlobBuilder();
            peBuilder.Serialize(peBlob);
            peBlob.WriteContentTo(peStream);
        }

        public FieldTypeEncoder Field(
            Action<FieldTypeEncoder> builder)
        {
            FieldTypeEncoder encoder = new BlobEncoder(new BlobBuilder())
                .Field();
            builder.Invoke(encoder);
            return encoder;
        }

        public SignatureTypeEncoder FieldSignature(
            Action<SignatureTypeEncoder> builder)
        {
            SignatureTypeEncoder encoder = new BlobEncoder(new BlobBuilder())
                .FieldSignature();
            builder.Invoke(encoder);
            return encoder;
        }

        public GenericTypeArgumentsEncoder MethodSpecificationSignature(
            MethodSpecificationSignatureParameters parameters,
            Action<GenericTypeArgumentsEncoder> builder)
        {
            GenericTypeArgumentsEncoder encoder = new BlobEncoder(new BlobBuilder())
                .MethodSpecificationSignature(
                parameters.GenericArgumentCount);
            builder.Invoke(encoder);
            return encoder;
        }

        public MethodSignatureEncoder MethodSignature(
            MethodSignatureParameters parameters,
            Action<MethodSignatureEncoder> builder)
        {
            MethodSignatureEncoder encoder = new BlobEncoder(new BlobBuilder())
                .MethodSignature(
                    parameters.Convention,
                    parameters.GenericParameterCount,
                    parameters.IsInstanceMethod);
            builder.Invoke(encoder);
            return encoder;
        }

        public MethodSignatureEncoder PropertySignature(
            PropertySignatureParameters parameters,
            Action<MethodSignatureEncoder> builder)
        {
            MethodSignatureEncoder encoder = new BlobEncoder(new BlobBuilder())
                .PropertySignature(parameters.IsInstanceProperty);
            builder.Invoke(encoder);
            return encoder;
        }

        public LocalVariablesEncoder LocalVariableSignature(
            LocalVariableSignatureParameters parameters,
            Action<LocalVariablesEncoder> builder)
        {
            LocalVariablesEncoder encoder = new BlobEncoder(new BlobBuilder())
                .LocalVariableSignature(parameters.NumberOfVariables);
            builder.Invoke(encoder);
            return encoder;
        }

        public SignatureTypeEncoder TypeSpecificationSignature(
            Action<SignatureTypeEncoder> builder)
        {
            SignatureTypeEncoder encoder = new BlobEncoder(new BlobBuilder())
                .TypeSpecificationSignature();
            builder.Invoke(encoder);
            return encoder;
        }

        public PermissionSetEncoder PermissionSetBlob(
            PermissionSetBlobParameters parameters,
            Action<PermissionSetEncoder> builder)
        {
            PermissionSetEncoder encoder = new BlobEncoder(new BlobBuilder())
                .PermissionSetBlob(parameters.AttributeCount);
            builder.Invoke(encoder);
            return encoder;
        }

        public NamedArgumentsEncoder PermissionSetArguments(
            PermissionSetArgumentsParameters parameters,
            Action<NamedArgumentsEncoder> builder)
        {
            NamedArgumentsEncoder encoder = new BlobEncoder(new BlobBuilder())
                .PermissionSetArguments(parameters.ArgumentCount);
            builder.Invoke(encoder);
            return encoder;
        }

        public int MethodBody(
            MethodBodyParameters parameters,
            Action<InstructionEncoder> builder)
        {
            InstructionEncoder encoder = new InstructionEncoder(
                new BlobBuilder(),
                new ControlFlowBuilder());
            builder.Invoke(encoder);
            return _methodBodyStream.AddMethodBody(
                encoder,
                parameters.MaxStack,
                parameters.LocalVariablesSignature,
                parameters.Attributes,
                parameters.HasDynamicStackAllocation);
        }
    }
}